//
// Created by guoshunw on 1/27/2021.
/*
 * https://blog.csdn.net/smith24122412/article/details/84818125
 * https://blog.csdn.net/tarawin/article/details/101055067
 * https://www.jianshu.com/p/512ce6566f8a
 * https://www.cnblogs.com/charybdis/p/8848457.html
 *
 * https://github.com/quatanium/foscam-ios-sdk/blob/master/g726lib/g711.c
 *
 * 网上看了好几个地方关于G711 codec编解吗？有几个问题都没有清晰解释，列举如下：
 *    1. 如何采用alaw和mulaw的官方所说的计算公式 进行编解码操作？ 公式见这里：https://blog.csdn.net/smith24122412/article/details/84818125
 *    2. alaw 和mulaw的官方所说的计算公式，和真实采用的近似算法之间的关系
 *    3. 很多例子所给出的编解码表格，如果你手算一个特定数值，会发现abcd的值是对不上的（尤其是mulaw），没有人解释为什么？
 *    4. 近似值计算时，对于负数的处理，本文https://blog.csdn.net/tarawin/article/details/101055067给了同较好的解释，但还不够透彻。
 *    5. mulaw计算时要加偏移值0x84，没人给出很好的解释
 *
 *       上面的问题我都研究了一遍，总体都有答案了。后面有时间会专门写一篇文章来解释这些问题。这里先简单回答第5个问题：
 *       mulaw 14位去除号位后，能编码的总数为2^13=8192，而mulaw的实规定最大量化级为8159, 即编码范围为0-8159，即任何待编码数子可能出来
 *       而8192-8159=33(0x21)，将原始值+33后，整体范围变为33-8192，这样再去计算高位的段序号就比较方便了。
 *
 * */

#include "G711Codec.h"
#include <cstdio>
#include "Reference.h"

using namespace std;

/**
 *
 * Linear Input Code	Compressed Code
 *	------------------------	---------------
 *	0000000wxyza			000wxyz
 *	0000001wxyza			001wxyz
 *	000001wxyzab			010wxyz
 *	00001wxyzabc			011wxyz
 *	0001wxyzabcd			100wxyz
 *	001wxyzabcde			101wxyz
 *	01wxyzabcdef			110wxyz
 *	1wxyzabcdefg			111wxyz
 * */

uint8_t G711Codec::alawEncode(short data) {
    // get the sign bit
    auto sign = data & 0x8000;
    uint16_t revertMask = 0xd5;
    if (0 != sign) {
        revertMask ^= 0x80;
        data = -data - 1;
    }

    // alaw only handle 13 bit, remove the last 3 bit
    data >>= 3;
    // obtain the grav field
    uint8_t grav = 7;
    uint16_t mask = 0x800;
    while (0 == (data & mask)) {
        grav--;
        mask >>= 1;
    }
    data >>= (0 == grav) ? 1 : grav;
    data &= 0xf;

//    printf("encode sign=%0d, garv=%02x, data=%02x, mask=%x\n", sign >>12, grav, data, revertMask);
    uint8_t result = (grav << 4) | data;
    return result ^ revertMask;
}

short G711Codec::alawDecode(uint8_t data) {
    short sign = data & 0x80;

    data ^= 0x55;
    uint8_t grav = (data & 0x70) >> 4;
    data &= 0xf;

//    printf("decode sign=%d, garv=%02x, data=%02x, revertMask=%x\n", sign, grav, data, revertMask);
    data <<= 1;
    data += 1;
    if (grav > 1) data <<= (grav - 1);
    int result = (0x800 >> (7 - grav) | data) << 3;
    if (0 == sign) result = -result;
    return result;
}

/**
 *
 * In order to simplify the encoding process, the original linear magnitude
 * is biased by adding 33 which shifts the encoding range from (0 - 8158) to
 * (33 - 8191). The result can be seen in the following encoding table:
 *
 *	Biased Linear Input Code	Compressed Code
 *	------------------------	---------------
 *	00000001wxyza			000wxyz
 *	0000001wxyzab			001wxyz
 *	000001wxyzabc			010wxyz
 *	00001wxyzabcd			011wxyz
 *	0001wxyzabcde			100wxyz
 *	001wxyzabcdef			101wxyz
 *	01wxyzabcdefg			110wxyz
 *	1wxyzabcdefgh			111wxyz
 *
 * Each biased linear code has a leading 1 which identifies the segment
 * number. The value of the segment number is equal to 7 minus the number
 * of leading 0's. The quantization interval is directly available as the
 * four bits wxyz.  * The trailing bits (a - h) are ignored.
 *
 * Ordinarily the complement of the resulting code word is used for
 * transmission, and so the code word is complemented before it is returned.
 *
 * For further information see John C. Bellamy's Digital Telephony, 1982,
 * John Wiley & Sons, pps 98-111 and 472-476.
 * */

#define CLIP 8159

uint8_t G711Codec::mulawEncode(short data) {
    // get the sign amd the magnitude of the value, u-law 14 bit
    data >>= 2;
    int16_t mask;
    if (data < 0) {
        data = -data;
        mask = 0x7f;
    } else {
        mask = 0xff;
    }

    if (data > CLIP)data = CLIP;
    // count zero
    uint8_t grav = 7;
    uint16_t m = 0x1000;
    while (0 == (data & m)) {
        grav--;
        m >>= 1;
    }

    data += 33;
    data >>= (grav + 1);
    data &= 0xf;
//    printf("grav=%d, data=%x\n", grav, data);
    return ((grav << 4) | data) ^ mask;
}

short G711Codec::mulawDecode(uint8_t data) {
    data = ~data;
    short signFlag = data & 0x80;
    auto grav = (data & 0x70) >> 4;
    auto abcd = data & 0xf;
#if 0
    const uint16_t BIAS = 33 << 2;
    short ret = (abcd << 3) + BIAS;
    ret <<= grav;
//    printf("grav=%d, abcd=%x, ret=%x\n", grav, abcd, ret);
    return 0!=signFlag ? BIAS - ret : ret - BIAS;
#else
    short ret = abcd << 4 | 8;
    if (grav > 0) {
        ret |= 0x100; // 在样本位的头添1
        ret <<= (grav - 1);
    }
    return 0 == signFlag ? ret : -ret;
#endif
}

/**

1753=0x6d9 = 000 00110 1011 1001
             000 00    1101 0111

s=0
g=3
abcd=1010
compressed = 1011 1010=0xda

 * For alaw:
 * 1753 = 0x6d9
 *        0000 0110 1101 1001
 *        0000 0110 1101 1
 *        0 00001 1011 011
 *        0 011   1011 ^ 0xd5 = coded(D5 is the sign bit and the odd bit reverse)
 *
 * For mulaw:
 * 1753 = 0x6d9
 *        0000 0110 1101 1001
 *        0000 0110 1101 10
 *        0000 0001 1011 0110      + 33
 *        0000 0001 1101 0111
 *        00 0001 1101 0111
 *        0 00001 1101 0111
 *        0 011   1101 ^ 0xff
 **/


/**
 * Refernece:
 *
 * */
void testG711Codec() {
    short src = 1753;
//    short src = -1753;
//    short src = 1234;
    uint8_t coded = G711Codec::alawEncode(src);
    auto c1 = linear2alaw(src);
    auto ucoded = G711Codec::mulawEncode(src);
//    ucoded = linear2ulaw(src);
    short decoded = G711Codec::alawDecode(coded);
    auto d1 = alaw2linear(c1);
//    auto udecoded = ulaw2linear(ucoded);
    short udecoded = G711Codec::mulawDecode(ucoded);

    printf("src=%d, codec=%2x, ucoded=%2x, c1=%2x, decoded=%0d, udecoded=%d, d1=%0d\n", src, coded, ucoded, c1, decoded,
           udecoded, d1);
//    printf("src=%04x, codec=%2x, decoded=%04x\n", src, coded, decoded);
}
